﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using FinPlusInterfaces;

namespace FinPlusAssembler
{
    //DI class can be used in an IOC fashion but this class is for DI, not for transient 
    //construction, primary use is to construct services and views from Pods. Static extension to 
    //construct objects from Pods has reposity but no container re generic sense.
    public static class DI
    {
        private static volatile ConcurrentDictionary<string, Repository> _repository = new ConcurrentDictionary<string, Repository>();

        //public
        public static IPod Construct(this IPods pods, string name, Env env, object[,] inject = null)
        {
            try
            {
                IPod res = null;
                var register = pods.Register(name, env);
                var objects = inject.ToDict();

                foreach (var component in pods.Values)
                {
                    if (res == null && component.Get(PodField.Id.ToString()).ToString().Equals(name))
                        res = component;
                    component.Construct(name, register, objects);
                }

                return res;
            }
            catch (Exception e)
            {
                throw new AssemblerException(string.Format("DI failed to construct {0}", name), e);
            }
        }

        public static IPods Exceptions(this IPods pods)
        {
            var exceptions = IOC.New<IPods>();

            foreach (var component in pods.Values)
                if (component.Object.GetType() == typeof(Exception))
                    exceptions.TryAdd(component.Id, component);

            return exceptions;
        }

        public static IPods NullObjects(this IPods pods)
        {
            var nullObject = IOC.New<IPods>();

            foreach (var component in pods.Values)
                if (component.Object == null)
                    nullObject.TryAdd(component.Id, component);

            return nullObject;
        }

        public static object InvokeMethod(this IPod pod, IPod method, IList<string> exclude = null, IDictionary<string, object> inject = null)
        {
            try
            {
                pod.Invoke = string.Format("{0}{1}", method.Name, pod.ToParam(exclude).StringFrom1d(','));
                var methodInfo = method.Type.GetMethod(pod.Name);
                var parms = Solve(pod.ToParam(exclude), methodInfo.GetParameters(), _repository[pod.LinkedId], inject);
                if (parms == null)
                    throw new TemplateException(string.Format("method params do not match {0}.{1}}", pod.Name));

                pod.Object = methodInfo.Invoke(method.Object, parms);
            }
            catch (Exception e)
            {
                pod.Object = new Exception(string.Format("IOC invoke failed {0}", pod.Invoke), e);
            }

            return pod.Object;
        } 

        public static void Dispose(string linkedId)
        {
            Repository register;
            if (!_repository.TryRemove(linkedId, out register))
                return;

            foreach (var component in register.Template.Values)
            {
                if (component.Object != null && component.Type != null)
                {
                    var methodInfo = component.Type.GetMethod("Dispose");
                    if (methodInfo != null)
                        methodInfo.Invoke(component.Object, null);
                }
            }
        }

        //private
        private static Repository Register(this IPods pods, string name, Env env)
        {
            try
            {
                Repository container;
                if (_repository.ContainsKey(pods.Id))
                    return _repository[pods.Id];

                _repository[pods.Id] = container = new Repository(pods, name, env, pods.Id);

                return container;
            }
            catch (Exception e)
            {
                throw new TemplateException("unable to register container", e);
            }
        }

        private static void Construct(this IPod pod, string config, Repository repository, IDictionary<string, object> inject = null)
        {
            try
            {
                if (pod.Object != null || pod.Name.Equals("VersionControl"))
                    return;

                pod.Object = new Exception("not invoked");
                pod.Invoke = string.Format("{0}({1})", pod.Name, pod.ToParam().StringFrom1d(','));
                pod.Type = repository.Type(pod.Name);
                var parms = pod.Solve(inject);

                //component.Object = Activator.CreateInstance(component.Type, parms);
                pod.Object = pod.Type.Construct<object>(parms);
                repository.Tag(pod.Object);

                FinPlusStateInject.InjectState(pod, IOC.New<IBean>(0, -1, PodField.Id.ToString(), pod[PodField.Id.ToString()].ToString(), DateTime.Now, ""));
                pod.InlineDock();

                return;
            }
            catch (Exception e)
            {
                pod.Object = e;
            }
        }

        private static void InlineDock(this IPod pod)
        {
            try
            {
                var dockBean = pod.Get("Dock");
                if (dockBean == null)
                    return;

                foreach (var dock in dockBean.ToString().ToJaggedArray(';', ':'))
                {
                    var dockPod = IOC.New<IPod>("Dock");
                    dockPod.Get("Arg1", dock[0]);
                    dockPod.Get("Arg2", dock.Length > 1 ? dock[1] : dock[0]);
                    dockPod.LinkedId = pod.LinkedId;
                    dockPod.InvokeMethod(pod);
                }
            }
            catch (Exception e)
            {
                pod.Object = new Exception(string.Format("IOC invoke failed dock {0}", pod.Name), e);
            }
        }

        private static object[] Solve(this IPod pod, IDictionary<string, object> inject)
        {
            object[] parms;
            var register =  _repository[pod.LinkedId];
            foreach (var constr in pod.Type.GetConstructors())
            {
                parms = Solve(pod.ToParam(), constr.GetParameters(), register, inject);
                if (parms != null)
                    return parms;
            }

            throw new TemplateException(string.Format("failed to invoke {0}", pod.Name));
        }

        private static object This(Repository repository, string name)
        {
            var component = repository.Object(name);
            if (component == null)
                return null;

            if (component.Object == null)
                component.Construct("", repository);

            return component.Object;
        }

        private static object[] Solve(object[] parms, ParameterInfo[] infos, Repository repository, IDictionary<string, object> inject)
        {
            try
            {
                var solvedParms = new object[infos.Length];
                
                foreach (var paramInfo in infos)
                {
                    if (inject != null && inject.ContainsKey(paramInfo.Name))
                    {
                        solvedParms[paramInfo.Position] = inject[paramInfo.Name];
                    }
                    else if (parms.Length <= paramInfo.Position)
                    {
                        if (!paramInfo.HasDefaultValue)
                            return null;
                        solvedParms[paramInfo.Position] = (object)paramInfo.DefaultValue;
                    }
                    else
                        solvedParms[paramInfo.Position] = Solve(paramInfo.ParameterType, parms[paramInfo.Position], repository, inject);
                }

                return solvedParms;
            }
            catch
            {
                return null;//try another constructor
            }
        }

        private static object Solve(this Type type, object val, Repository repository, IDictionary<string, object> inject)
        {
            if (val.ToString().StartsWith("this."))
                return This(repository, val.ToString().Split('.')[1]);

            if (val.ToString().StartsWith("inject."))
                return inject[val.ToString().Split('.')[1]];

            if (val.GetType().Equals(type))
                return val;

            if (type.IsArray)
                return type.SolveArray(val, repository, inject);

            if (type.IsTypeOf<IDictionary>())
                return type.SolveDictionary(val, repository, inject);

            if (type.IsTypeOf<IList>())
                return type.SolveList(val, repository, inject);

            if (type.IsEnum)
                return Enum.Parse(type, val.ToString());

            return type.Solve(val, true);
        }

        private static object SolveArray(this Type type, object val, Repository repository, IDictionary<string, object> inject)
        {
            var elementType = type.GetElementType();

            var vals = val.GetType().IsArray ? (object[])val : val.ToString().ToArray1d(',', "{}");
            var obj = new ArrayList();

            for (int i = 0; i < vals.Length; i++)
                obj.Add(Solve(elementType, vals[i], repository, inject));

            return obj.ToArray(elementType);
        }

        private static object SolveDictionary(this Type type, object val, Repository repository, IDictionary<string, object> inject)
        {
            try
            {
                if (val.ToString().Equals(string.Empty)) return null;
                
                var args = type.GetGenericArguments();
                var keyType = args[0].UnderlyingSystemType;
                var valueType = args[1].UnderlyingSystemType;
               
                var dictType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
                var res = (IDictionary)Activator.CreateInstance(dictType);

                if(valueType.IsArray)
                    foreach(var kvp in val.ToString().ToDictOfArrays(',', "{}"))
                        res[keyType.Solve(kvp.Key, repository, inject)] = valueType.Solve(kvp.Value, repository, inject);
                else
                    foreach (var kvp in val.ToString().ToDict(',', "{}"))
                        res[keyType.Solve(kvp.Key, repository, inject)] = valueType.Solve(kvp.Value, repository, inject);

                return res;
            }
            catch (Exception e)
            {
                throw new TemplateException(string.Format("invalid parse {0}", val.ToString()), e);
            }
        }

        private static object SolveList(this Type type, object val, Repository repository, IDictionary<string, object> inject)
        {
            try
            {
                if (val.ToString().Equals(string.Empty))
                    return null;

                var array = val.ToString().ToArray1d(',', "{}");

                var argType = type.GetGenericArguments()[0];
                var listType = typeof(List<>).MakeGenericType(argType);
                var list = (IList)Activator.CreateInstance(listType);

                foreach (var parm in array)
                    list.Add(argType.Solve(parm, repository, inject));

                return list;
            }
            catch (Exception e)
            {
                throw new TemplateException(string.Format("invalid parse {0}", val.ToString()), e);
            }
        }

        private static object Solve(this Type type, object val, bool dateToInt = false)
        {
            if (val.Equals(string.Empty))
                return null;
            
            var type2 = val.GetType();
            if (type2.Equals(type))
                return val;

            if (dateToInt && type2.Equals(typeof(DateTime)))//Temp solution re QLProxy, QLProxy will be deprecated
                return (int)((DateTime)val).ToOADate();

            var parse = type.GetMethod("Parse", new[] { typeof(string) });
            if (parse != null)
                return parse.Invoke(null, new object[] { val });

            return Convert.ChangeType(val, type);
        }
    }
}

























































































































































































//Copyright2013<jnrton>AllRightsReserved 
//Contact j.nrton@finpus